package algo3c3g3.controller;

import java.io.*;
import algo3c3g3.model.*;
import algo3c3g3.view.*;
import algo3c3g3.model.persistence.*;
import org.jdom.JDOMException;

/**
 * Controlador del programa. Contiene metodos para ejecutar todas las acciones
 * necesarias del juego.
 */
public class GameController implements Controller
{
    private Level level;
    private ActionController actionController;
    private long sleepTime = 300;
    private boolean paused = false;
    private static final int ADD_UNIT_INTERVAL = 4;
    private static final int TIME_INTERVAL = 2;
    private int addUnitInterval;
    private int addTimeInterval;
    private boolean active;
    private View view;
    
    /**
     * @param level Modelo de datos sobre el que este controlador actua.
     */
    public GameController(Level level)
    {
        this.level = level;
        actionController = new ActionController(level);
    }
    
    /**
     * Devuelve una referencia al controlador de acciones.
     */
    public ActionController getActionController()
    {
        return actionController;
    }
    
    /**
     * Inicia el bucle del juego, donde ocurre el movimiento de las acciones,
     * se agregan unidades y se cuenta el tiempo del juego.
     */
    private void play()
    {
        active = true;
        
        System.out.println("PLAY THREAD:" + Thread.currentThread().getName());
        
        // Bucle del juego
        while(active)
        {
            try
            {
                Thread.sleep(sleepTime);
            }
            catch(InterruptedException e)
            {
                return;
            }
            
            if(!paused)
            {
                level.getWorld().moveUnits();
                addUnit();
                gameTime();
            }
            
            checkGameOver();
        }
        
        System.out.println("STOP PLAY THREAD:" + Thread.currentThread().getName());
    }
    
    private void addUnit()
    {
        // Si la cantidad de unidades agregadas es menor al limite de unidades
        if(level.getWorld().getUnitQty() < level.getMaxUnitQty())
        {
            addUnitInterval++;
            if(addUnitInterval == ADD_UNIT_INTERVAL)
            {
                level.getWorld().addUnit();
                addUnitInterval = 0;
            }
        }
    }
    
    private void gameTime()
    {
        if (level.getTime() < level.getTiemOut())
        {
            addTimeInterval++;
            if (addTimeInterval == TIME_INTERVAL)
            {
                level.incrementTime();
                addTimeInterval = 0;
            }
            
            view.updateTime();
        }
    }
    
    private void checkGameOver()
    {
        World world = level.getWorld();
        
        // Si aun se cuenta con tiempo para jugar
        if (level.getTime() == level.getTiemOut())
        {
            if (view != null)
                view.showInfoMsessage("Perdiste. Se acabo el tiempo", "Juego terminado");
            
            stop();
            view.resetScreen();
        }
        
        if ((world.getUnitQty() == level.getMaxUnitQty()) && // Si ya salieron todas las unidades
            (world.activeUnits() == 0)) // y no hay unidades activas

        {
            // Si se salvo al minimo de unidades
            if (world.savedUnits() >= level.getUnitQtyToSave())
            {
                if (view != null)
                    view.showInfoMsessage("Ganaste", "Juego terminado");
            }
            // No se salvo al minimo
            else
            {
                if (view != null)
                    view.showInfoMsessage("Perdiste", "Juego terminado");
            }
            
            // Detiene el bucle del juego
            stop();

            // Inidca a la vista que el juego termino.
            view.resetScreen();
        }
    }
        
    private void stop()
    {
        active = false;
    }
    
    /**
     * Pausa el juego.
     */
    public void setPause(boolean value)
    {
        paused = value;
    }
    
    public boolean isPaused()
    {
        return paused;
    }
    
    /**
     * Registra una vista a este controlador.
     * Solo puede haber una vista registrada a la vez.
     */
    public void registerView(View view)
    {
        this.view = view;
    }
    
    /**
     * Guarda en un arhicvo XML el nivel registrado en este controlador.
     * @param file Achivo XML
     */
    public void saveLevel(File file)
    {
        boolean wasPaused = isPaused();
        
        if(wasPaused == false)
            setPause(true);
        
        Persistence.toXMLFile(level.save(), file.getPath() + ".xml");
        
        if(wasPaused == false)
            setPause(false);                                                                                                                                        
    }
    
    /**
     * Carga un nivel desde un archivo XML.
     * @param file Archivo XML
     * @return Level cargado
     */
    public static Level loadLevel(File file) throws JDOMException,
                                                    IOException
    {
        return new Level(Persistence.getFromXML(file));
    }
    
    // Carga nivel de prueba.
    public static Level loadTestLevel()
    {
        return TestLevelFactory.newTestLevel3();
    }
    
    /**
     * Inicia un juego nuevo.
     * Deberia crearse una instancia nueva de GameController antes de llamar a este metodo.
     * Este metodo crea un thread nuevo y ejecuta el bucle de juego en el,
     * por lo que puede ser llamado desde el thread de eventos de la vista.
     */
    public void startGame()
    {
        System.out.println("START CALLER THREAD:" + Thread.currentThread().getName());
        
        Thread game = new Thread(new Runnable()
        {
            public void run()
            {
                System.out.println("START NEW THREAD:" + Thread.currentThread().getName());
                GameController.this.play();
            }
        });
        
        game.start();
    }
    
    
    /**
     * Detiene el bucle de juego.
     * Este metodo debe ser llamado antes de crear un nuevo juego con startGame()
     * para que termine el bucle de juego del thread anterior.
     */
    public void stopGame()
    {
        stop();
    }
}
